package org.robovm.idea.components.setupwizard; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; import java.lang.reflect.Method; import java.net.URL; import java.net.URLConnection; import java.util.zip.GZIPInputStream; import javax.swing.*; import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.io.IOUtils; import org.zeroturnaround.zip.NameMapper; import org.zeroturnaround.zip.ZipUtil; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.fileChooser.FileChooserDialog; import com.intellij.openapi.fileChooser.FileChooserFactory; import com.intellij.openapi.projectRoots.ProjectJdkTable; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.uiDesigner.core.GridConstraints; import com.intellij.uiDesigner.core.GridLayoutManager; public class AndroidBundledSetupDialog extends JDialog { private String sdkDir; enum OS { MacOsX, Windows, Linux } private static final String ANDROID_SDK_URL_MACOSX = "http://download.robovm.org/android-sdks/android-sdk-macosx-23-rvm-1.8.tar.gz"; private static final String ANDROID_SDK_URL_WINDOWS = "http://download.robovm.org/android-sdks/android-sdk-windows-23-rvm-1.8.zip"; private static final String ANDROID_SDK_URL_LINUX = "http://download.robovm.org/android-sdks/android-sdk-linux-23-rvm-1.8.tar.gz"; private static OS os; private static String ANDROID_SDK_URL; static { if(System.getProperty("os.name").contains("Mac")) { ANDROID_SDK_URL = ANDROID_SDK_URL_MACOSX; os = OS.MacOsX; } else if(System.getProperty("os.name").contains("Windows")) { ANDROID_SDK_URL = ANDROID_SDK_URL_WINDOWS; os = OS.Windows; } else if(System.getProperty("os.name").contains("Linux")) { ANDROID_SDK_URL = ANDROID_SDK_URL_LINUX; os = OS.Linux; } } private JPanel panel; private JLabel infoText; private JButton nextButton; private JButton installAndroidSdkButton; private JPanel installPanel; public AndroidBundledSetupDialog() { setContentPane(panel); setModalityType(ModalityType.APPLICATION_MODAL); setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); setTitle("RoboVM Setup"); infoText.setText("<html>Install the Android SDK if you want to develop for both iOS and Android.</br>"); installAndroidSdkButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { installAndroidSdk(); } }); nextButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { dispose(); } }); pack(); setLocationRelativeTo(null); } private void installAndroidSdk() { nextButton.setEnabled(false); downloadAndroidSdk(); } private void downloadAndroidSdk() { installPanel.removeAll(); GridLayoutManager layout = new GridLayoutManager(2, 1); installPanel.setLayout(layout); final JLabel label = new JLabel("Downloading Android SDK ..."); final JProgressBar progressBar = new JProgressBar(); progressBar.setMinimum(0); progressBar.setMaximum(100); GridConstraints labelConsts = new GridConstraints(); labelConsts.setFill(GridConstraints.FILL_HORIZONTAL); GridConstraints progressConsts = new GridConstraints(); progressConsts.setRow(1); progressConsts.setFill(GridConstraints.FILL_HORIZONTAL); installPanel.add(label, labelConsts); installPanel.add(progressBar, progressConsts); installPanel.revalidate(); installAndroidSdkButton.setText("Cancel"); for (ActionListener listener : installAndroidSdkButton.getActionListeners()) { installAndroidSdkButton.removeActionListener(listener); } final BooleanFlag cancel = new BooleanFlag(); installAndroidSdkButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { cancel.setValue(true); } }); final String sdkDir = getSdkDir(); new Thread(new Runnable() { @Override public void run() { try { // Download our bundle downloadAndroidSdk(sdkDir, progressBar, label, cancel); // Setup the Android SDK for our installation dir SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { String clazz = "com.android.tools.idea.sdk.DefaultSdks"; String method = "createAndroidSdksForAllTargets"; try { Class cls = AndroidBundledSetupDialog.class.getClassLoader().loadClass(clazz); Method mtd = cls.getMethod(method, File.class); mtd.invoke(null, new File(sdkDir)); } catch (Throwable t) { clazz = "com.android.tools.idea.sdk.IdeSdks"; method = "createAndroidSdkPerAndroidTarget"; try { Class cls = AndroidBundledSetupDialog.class.getClassLoader().loadClass(clazz); Method mtd = cls.getMethod(method, File.class); mtd.invoke(null, new File(sdkDir)); } catch (Throwable t2) { t2.printStackTrace(); System.out.println("Couldn't create Android SDK"); } } } }); } }); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { AndroidBundledSetupDialog.this.dispose(); } }); } catch (final Throwable e) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { label.setForeground(Color.red); String msg = e.getMessage().substring(0, Math.min(50, e.getMessage().length() - 1)) + " ..."; label.setText("Couldn't install Android SDK: " + msg); installAndroidSdkButton.setEnabled(false); nextButton.setEnabled(true); } }); } } }).start(); } private void downloadAndroidSdk(final String sdkDir, final JProgressBar progressBar, final JLabel label, final BooleanFlag cancel) throws IOException { // download the SDK zip to a temporary location File destination = File.createTempFile("android-sdk", ".zip"); destination.deleteOnExit(); URL url = new URL(ANDROID_SDK_URL); URLConnection con = url.openConnection(); final long length = con.getContentLengthLong(); byte[] buffer = new byte[1024*100]; try (InputStream in = con.getInputStream(); OutputStream out = new BufferedOutputStream(new FileOutputStream(destination))) { int read = in.read(buffer); long total = read; while(read != -1) { out.write(buffer, 0, read); read = in.read(buffer); total += read; final int percentage = (int)(((double)total / (double)length) * 100); final long totalRead = total; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { progressBar.setValue(Math.min(100, percentage)); label.setText("Downloading Android SDK (" + (totalRead / 1024 / 1024) + "/" + (length / 1024 / 1024) + "MB)"); } }); if(cancel.getValue()) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { installAndroidSdkButton.setText("Install Android SDK"); for(ActionListener listener: installAndroidSdkButton.getActionListeners()) { installAndroidSdkButton.removeActionListener(listener); } installAndroidSdkButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { installAndroidSdk(); } }); installPanel.removeAll(); installPanel.revalidate(); nextButton.setEnabled(true); } }); return; } } } // unpack the SDK zip, then rename the extracted // folder SwingUtilities.invokeLater(new Runnable() { @Override public void run() { label.setText("Unpacking SDK to " + sdkDir); installAndroidSdkButton.setEnabled(false); nextButton.setEnabled(false); } }); final File outputDir = new File(sdkDir); File tmpOutputDir = new File(outputDir.getParent()); if(!tmpOutputDir.exists()) { if(!tmpOutputDir.mkdirs()) { throw new RuntimeException("Couldn't create output directory"); } } if(ANDROID_SDK_URL.endsWith("zip")) { ZipUtil.unpack(destination, tmpOutputDir, new NameMapper() { @Override public String map(String s) { int idx = s.indexOf("/"); s = outputDir.getName() + s.substring(idx); final String file = s; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { label.setText("Unpacking " + file.substring(0, Math.min(50, file.length() - 1)) + " ..."); } }); return s; } }); } else { TarArchiveInputStream in = null; try { in = new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(destination))); TarArchiveEntry entry = null; while ((entry = (TarArchiveEntry)in.getNextEntry()) != null) { File f = new File(tmpOutputDir, entry.getName()); if (entry.isDirectory()) { f.mkdirs(); } else { f.getParentFile().mkdirs(); OutputStream out = null; try { out = new FileOutputStream(f); IOUtils.copy(in, out); } finally { IOUtils.closeQuietly(out); } if((entry.getMode() & 0100) != 0) { f.setExecutable(true); } } final String fileName = entry.getName(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { label.setText("Unpacking " + fileName.substring(0, Math.min(50, fileName.length() - 1)) + " ..."); } }); } } catch (Throwable t) { // can't do anything here } finally { IOUtils.closeQuietly(in); } } // ziputils doesn't preserve file permissions for(File file: new File(outputDir, "tools").listFiles()) { if(file.isFile()) { file.setExecutable(true); } } for(File file: new File(outputDir, "platform-tools").listFiles()) { if(file.isFile()) { file.setExecutable(true); } } for(File file: new File(outputDir, "tools/proguard/bin").listFiles()) { if(file.isFile()) { file.setExecutable(true); } } } private static class BooleanFlag { volatile boolean value; public void setValue(boolean value) { this.value = value; } public boolean getValue() { return this.value; } } public static String getSdkDir() { return new File(new File(System.getProperty("user.home")), "/Library/RoboVM/android-sdk").getAbsolutePath(); } private void createUIComponents() {} }